Plasma only samples

import pandas as pd
samples = pd.read_csv('data/sample_sheet.csv')
# must be Healthy Control Study and must pass MiSeq QC
samples = samples.loc[(samples['Study'] == 'Healthy Controls') & (samples['MISEQ.QC.PASS'] == 'PASS')]  
samples = samples.set_index('MT.Unique.ID').sort_values(by=['Participant.ID', 'Source'])
# capitalize & strip whitespace for consistency
for column in ['Gender', 'Race', 'Source']:
    samples[column] = samples[column].str.capitalize()
    samples[column] = samples[column].str.strip()
# use correct ontology terms
race_ontology = {'Asian': 'Asian',
 'Black or african american': 'African_American',
 'Mixed/asian & white': 'Multiracial',
 'Mixed/asian &black': 'Multiracial',
 'Mixed/black, white, asian': 'Multiracial',
 'Native hawiian or other pacific islander': 'Pacific_Islander',
 'Pacific islander': 'Pacific_Islander',
 'White': 'White'}
for id in samples.index:
    race = samples.at[id, 'Race']
    samples.at[id, 'Race'] = race_ontology[race] if race in race_ontology else 'Multiracial'
# get only the plasma samples
mir_counts = pd.read_csv("data/get_canonical/canon_mir_counts.csv", index_col=0)
samples.index = pd.Index(['X' + str(row) for row in samples.index])
plasma_samples = samples.loc[samples['Source'] == "Plasma"]
plasma_mir_counts = mir_counts[plasma_samples.index]
plasma_samples.to_csv('data/plasma_samples.csv')
plasma_mir_counts.to_csv('data/plasma_mir_counts.csv')

Load the sample data and miRNA counts

samples <- read.csv('data/plasma_samples.csv')
counts <- read.csv('data/plasma_mir_counts.csv')
samples <- subset(samples, Library.Generation.Set != "SetRecheck" )  # exclude SetRecheck samples
samples <- samples[samples$X != "X11" & samples$X != "X92",] # remove outliers
samples$Participant.ID <- factor(samples$Participant.ID)  # ID is categorical, not numerical
rownames(counts) = counts$X
rownames(samples) = samples$X
counts$X <- NULL  # remove extra column
samples$X <- NULL
counts <- counts[,unique(rownames(samples))]
head(samples)
head(counts)

Filter

library(edgeR)
design <- model.matrix(~0+Race+Gender+Age, samples)
dge = DGEList(counts = counts, samples = samples)
# require miRNAs to have CPM > 1 in at least 2 samples
countsPerMillion <- edgeR::cpm(dge)
countCheck <- countsPerMillion > 1
head(countCheck)
                 X1    X2    X3    X4    X5    X6    X7    X8    X9   X10   X14   X15   X16   X17   X18   X19   X20   X21   X22   X23   X25
hsa-let-7a-3p  TRUE  TRUE FALSE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE
hsa-let-7a-5p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7b-3p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7b-5p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7c-3p FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
hsa-let-7c-5p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
                X26  X27   X28   X29   X30   X31   X32   X33   X34   X35   X36   X37   X38   X39   X40   X41  X42   X43   X44   X45   X46   X48
hsa-let-7a-3p  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7a-5p  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7b-3p  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7b-5p  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7c-3p FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE
hsa-let-7c-5p  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
                X49  X50   X52   X53   X54  X55   X56   X57   X58   X59   X60   X61  X62   X63   X64   X65   X66   X67   X68   X69   X76   X77
hsa-let-7a-3p  TRUE TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7a-5p  TRUE TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7b-3p  TRUE TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7b-5p  TRUE TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7c-3p FALSE TRUE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
hsa-let-7c-5p  TRUE TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
                X79  X80   X81   X82   X83   X84   X85  X86   X87   X88   X89   X90   X91  X93   X96   X99  X100  X102  X103  X104  X105  X106
hsa-let-7a-3p  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7a-5p  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7b-3p  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7b-5p  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7c-3p FALSE TRUE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
hsa-let-7c-5p  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
keep <- which(rowSums(countCheck) >= 2) 
dge <- dge[keep,]

Explore variance

library(SingleCellExperiment)
library(scater)
reads_sce <- SingleCellExperiment(assays=list(counts=dge$counts),  colData=dge$samples)
# remove unexpressed miRNAs
keep_feature <- rowSums(counts(reads_sce) > 0) > 0
reads_sce <- reads_sce[keep_feature, ]
reads_sce <- calculateQCMetrics(reads_sce)
Note that the names of some metrics have changed, see 'Renamed metrics' in ?calculateQCMetrics.
Old names are currently maintained for back-compatibility, but may be removed in future releases.
# log transform
cpm(reads_sce) <- calculateCPM(reads_sce)
reads_sce <- normalize(reads_sce)
using library sizes as size factors
logcounts(reads_sce) <- log2(calculateCPM(reads_sce) + 1)
# visualize
hist(reads_sce$total_counts, breaks=100)  # counts per sample

hist(reads_sce$total_features, breaks=100)  # counts per miRNA

plotQC(reads_sce, type = "highest-expression")

plotQC(reads_sce, type="explanatory-variables", variables=c("Index", "Participant.ID", "Collection.Date", "Library.Generation.Set", "MiSeq.QC.Run", 'total_features', "Age", "Race", "Sex"))
variable Sex not found in colData(object).
                     Please make sure colData(object)[, variable] exists. This variable will not be plotted.

Examine sources of variation

plotPCA(reads_sce, exprs_values = "logcounts", colour_by = "Library.Generation.Set", size_by = "total_features")
non-plotting arguments like 'exprs_values' should go in 'run_args'

plotPCA(reads_sce, exprs_values = "logcounts", colour_by = "Race", shape_by="Gender", size_by = "total_features")
non-plotting arguments like 'exprs_values' should go in 'run_args'

for (var in c("total_features", "Age", "Library.Generation.Set", "Index", "Participant.ID", "Race", "Gender", "Collection.Date")) {
  print(
    plotQC(reads_sce, type = "find-pcs", exprs_values = "logcounts", variable = var)
    )  + ggtitle(var) 
  }

Normalize & Remove unwanted sources of variation

library(RUVSeq)
library(ggplot2)
library(mvoutlier)
dge <- calcNormFactors(dge)
dge <- estimateGLMCommonDisp(dge, design)
dge <- estimateGLMTagwiseDisp(dge, design)
fit <- glmFit(dge, design)
res <- residuals(fit, type="deviance")
set <- newSeqExpressionSet(dge$counts, phenoData=dge$samples)
ruvr_sets <- list()
for(k in 1:5) {
  ruvr_sets[[k]] <- RUVr(set, row.names(dge), k=k, res)
  assay(reads_sce, paste("RUVr k=", toString(k))) <- log2(t(t(assayData(ruvr_sets[[k]])$normalizedCounts) / colSums(assayData(ruvr_sets[[k]])$normalizedCounts) * 1e6) + 1)
}
for(n in assayNames(reads_sce)) {
  print(
        plotPCA(
            reads_sce,
            colour_by = "Library.Generation.Set",
            size_by = "total_features",
            exprs_values = n
        ) + ggtitle(paste("Batch",n))
  )
  print(
        plotPCA(
            reads_sce,
            colour_by = "Race",
            shape_by = "Gender",
            size_by = "total_features",
            exprs_values = n
        ) + ggtitle(paste("Demographics", n))        
  )
}
non-plotting arguments like 'exprs_values' should go in 'run_args'

Detect outliers

reads_sce <- runPCA(reads_sce, use_coldata = TRUE, detect_outliers = TRUE)
failed to find 'pct_counts_feature_control' in column metadatafailed to find 'total_features_by_counts_feature_control' in column metadatafailed to find 'log10_total_counts_endogenous' in column metadatafailed to find 'log10_total_counts_feature_control' in column metadata
outliers <- colnames(reads_sce)[reads_sce$outlier]
head(outliers)
character(0)

Examine sources of variance after removing unwanted variation

for (var in c("total_features", "Age", "Library.Generation.Set", "Index", "Participant.ID", "Source", "Race", "Gender", "Collection.Date")) {
  print(
    plotQC(reads_sce, type = "find-pcs", exprs_values = "RUVr k= 2", variable = var)
    )  + ggtitle(var) 
}
Error in findImportantPCs(object, ...) : 
  variable only has one unique value, so cannot determine important
             principal components.

Visualize top highly-expressed miRNAs male-vs-female

library(RColorBrewer)
library(reshape2)
# Ryan's code, modified
norm.expr.matr <- exprs(reads_sce)
# Rank the mean expression values for plasma/serum miRs. Highest expression = 1
mean.expr.male.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)$Gender=="Male"]))
mean.expr.female.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)$Gender=="Female"]))
top_N <- 20
top.miRs <- row.names(norm.expr.matr)[mean.expr.male.rank <= top_N | mean.expr.female.rank <= top_N] # get the names of the top miRs in plasma or serum
norm.expr.top <- norm.expr.matr[top.miRs, ] # Get the expression matrix for the top mIRs
norm.expr.melt <- reshape2::melt(norm.expr.top) # Convert your normalized expression matrix to a 3 column data.frame (row.name, col.name, expression value). Melt is in the dpylr package, I believe.
colnames(norm.expr.melt) <- c("miR.ID", "MT.Unique.ID", "norm.expr") # just so it's easier for me to tell you which columns I'm using.
norm.expr.melt$Gender <- ""
for (row_num in 1:nrow(norm.expr.melt)){  # Pull the Gender values from the column metadata.
  mt_unique_id <- norm.expr.melt[row_num, ]$MT.Unique.ID
  norm.expr.melt[row_num, "Gender"] <- as.character(samples[mt_unique_id,"Gender"])
}
# This would be a simple boxplot with plasma/serum side-by-side. Overlaying the boxes over points takes a little more tweaking to get the dodge/width right, but it's doable.
ggplot(norm.expr.melt, aes(x=reorder(miR.ID, norm.expr, FUN=median), y=norm.expr, fill=Gender)) + geom_boxplot(pos="dodge", outlier.size=0.5) + 
  ggtitle("Top 20 Expressed miRNAs") + ylab("Normalized Expression") + xlab("miRNA ID") + 
  theme(panel.grid.major.x = element_line(size = 0.5, linetype = 'solid', colour = "grey"), 
        panel.grid.major.y = element_blank(),
        axis.text.x = element_text(angle = 90, hjust = 1))

ggplot(norm.expr.melt, aes(x=reorder(miR.ID, norm.expr, FUN=median), y=norm.expr, fill=Gender)) + geom_boxplot(pos="dodge", outlier.size=0.5) + 
  ggtitle("Top 20 Expressed miRNAs") + ylab("Normalized Expression") + xlab("miRNA ID") + 
  theme(panel.grid.major.y = element_line(size = 0.5, linetype = 'solid', colour = "grey"), 
        panel.grid.major.x = element_blank()) + coord_flip()

Visualize top highly-expressed miRNAs by Race

DE analysis with EdgeR

design <- model.matrix(~ 0 + Race + dge$Gender$Male + dge$Gender$Female + Age + W_1 + W_2, pData(ruvr2))
Error in model.frame.default(object, data, xlev = xlev) : 
  invalid type (NULL) for variable 'dge$Gender$Male'

Contrast gender

lrt <- glmLRT(fit, coef = "GenderMale")
results <- data.frame(topTags(lrt, n=Inf, sort.by="PValue", p.value=0.05))
sig_miRs = list()
logFC_threshold <- 1
sig_miRs[[1]] <- rownames(results[results$PValue < 0.05 & abs(results$logFC) > logFC_threshold,])  # filter by p-value and logFC
sig_miRs[[2]] <- rownames(results[results$PValue < 0.05,])
# heatmaps of significant DE miRNAs
Gender <- pData(ruvr2)[,c("Gender")]
annotations <- as.data.frame(Gender)
rownames(annotations) <- rownames(pData(ruvr2))
for(miR_list in sig_miRs) {
  pheatmap(norm.expr.matr[miR_list,], cluster_rows=TRUE, show_rownames=TRUE, cluster_cols=TRUE, annotation_col=annotations, main="Differentially Expressed miRNAs")
}

Contrast race

lrt <- glmLRT(fit, contrast=makeContrasts(asn=(Asian-(African_American+White)/2),
                                          afr=(African_American-(Asian+White)/2),
                                          wht=(White-(African_American+Asian)/2)), levels = design)
results <- data.frame(topTags(lrt, n=Inf, sort.by="PValue", p.value=0.05))
sig_miRs = list()
logFC_threshold <- 1
sig_miRs[[1]] <- rownames(results[results$PValue < 0.05 & abs(results$logFC) > logFC_threshold,])  # filter by p-value and logFC
sig_miRs[[2]] <- rownames(results[results$PValue < 0.05,])
# heatmaps of significant DE miRNAs
Race <- pData(ruvr2)[,c("Race", "Gender")]
annotations <- as.data.frame(Race)
rownames(annotations) <- rownames(pData(ruvr2))
for(miR_list in sig_miRs) {
  pheatmap(norm.expr.matr[miR_list,], cluster_rows=TRUE, show_rownames=TRUE, cluster_cols=TRUE, annotation_col=annotations, main="Differentially Expressed miRNAs")
}

Create a PCA plot showing Age x Gender

plotPCA(
            reads_sce,
            colour_by = "Age",
            shape_by = "Gender",
            size_by = "total_features",
            exprs_values = "RUVr k= 2"
) + ggtitle("RUVSeq-Normalized Expression (k=2)") 
non-plotting arguments like 'exprs_values' should go in 'run_args'

save.image("diff_expr_plasma_only.RData")
LS0tCnRpdGxlOiAibWlSTkEgRGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgZm9yIHBsYXNtYSBzYW1wbGVzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCiMgUGxhc21hIG9ubHkgc2FtcGxlcwpgYGB7cHl0aG9uM30KaW1wb3J0IHBhbmRhcyBhcyBwZApzYW1wbGVzID0gcGQucmVhZF9jc3YoJ2RhdGEvc2FtcGxlX3NoZWV0LmNzdicpCiMgbXVzdCBiZSBIZWFsdGh5IENvbnRyb2wgU3R1ZHkgYW5kIG11c3QgcGFzcyBNaVNlcSBRQwpzYW1wbGVzID0gc2FtcGxlcy5sb2NbKHNhbXBsZXNbJ1N0dWR5J10gPT0gJ0hlYWx0aHkgQ29udHJvbHMnKSAmIChzYW1wbGVzWydNSVNFUS5RQy5QQVNTJ10gPT0gJ1BBU1MnKV0gIApzYW1wbGVzID0gc2FtcGxlcy5zZXRfaW5kZXgoJ01ULlVuaXF1ZS5JRCcpLnNvcnRfdmFsdWVzKGJ5PVsnUGFydGljaXBhbnQuSUQnLCAnU291cmNlJ10pCiMgY2FwaXRhbGl6ZSAmIHN0cmlwIHdoaXRlc3BhY2UgZm9yIGNvbnNpc3RlbmN5CmZvciBjb2x1bW4gaW4gWydHZW5kZXInLCAnUmFjZScsICdTb3VyY2UnXToKICAgIHNhbXBsZXNbY29sdW1uXSA9IHNhbXBsZXNbY29sdW1uXS5zdHIuY2FwaXRhbGl6ZSgpCiAgICBzYW1wbGVzW2NvbHVtbl0gPSBzYW1wbGVzW2NvbHVtbl0uc3RyLnN0cmlwKCkKIyB1c2UgY29ycmVjdCBvbnRvbG9neSB0ZXJtcwpyYWNlX29udG9sb2d5ID0geydBc2lhbic6ICdBc2lhbicsCiAnQmxhY2sgb3IgYWZyaWNhbiBhbWVyaWNhbic6ICdBZnJpY2FuX0FtZXJpY2FuJywKICdNaXhlZC9hc2lhbiAmIHdoaXRlJzogJ011bHRpcmFjaWFsJywKICdNaXhlZC9hc2lhbiAmYmxhY2snOiAnTXVsdGlyYWNpYWwnLAogJ01peGVkL2JsYWNrLCB3aGl0ZSwgYXNpYW4nOiAnTXVsdGlyYWNpYWwnLAogJ05hdGl2ZSBoYXdpaWFuIG9yIG90aGVyIHBhY2lmaWMgaXNsYW5kZXInOiAnUGFjaWZpY19Jc2xhbmRlcicsCiAnUGFjaWZpYyBpc2xhbmRlcic6ICdQYWNpZmljX0lzbGFuZGVyJywKICdXaGl0ZSc6ICdXaGl0ZSd9CmZvciBpZCBpbiBzYW1wbGVzLmluZGV4OgogICAgcmFjZSA9IHNhbXBsZXMuYXRbaWQsICdSYWNlJ10KICAgIHNhbXBsZXMuYXRbaWQsICdSYWNlJ10gPSByYWNlX29udG9sb2d5W3JhY2VdIGlmIHJhY2UgaW4gcmFjZV9vbnRvbG9neSBlbHNlICdNdWx0aXJhY2lhbCcKIyBnZXQgb25seSB0aGUgcGxhc21hIHNhbXBsZXMKbWlyX2NvdW50cyA9IHBkLnJlYWRfY3N2KCJkYXRhL2dldF9jYW5vbmljYWwvY2Fub25fbWlyX2NvdW50cy5jc3YiLCBpbmRleF9jb2w9MCkKc2FtcGxlcy5pbmRleCA9IHBkLkluZGV4KFsnWCcgKyBzdHIocm93KSBmb3Igcm93IGluIHNhbXBsZXMuaW5kZXhdKQpwbGFzbWFfc2FtcGxlcyA9IHNhbXBsZXMubG9jW3NhbXBsZXNbJ1NvdXJjZSddID09ICJQbGFzbWEiXQpwbGFzbWFfbWlyX2NvdW50cyA9IG1pcl9jb3VudHNbcGxhc21hX3NhbXBsZXMuaW5kZXhdCnBsYXNtYV9zYW1wbGVzLnRvX2NzdignZGF0YS9wbGFzbWFfc2FtcGxlcy5jc3YnKQpwbGFzbWFfbWlyX2NvdW50cy50b19jc3YoJ2RhdGEvcGxhc21hX21pcl9jb3VudHMuY3N2JykKYGBgCgojIyBMb2FkIHRoZSBzYW1wbGUgZGF0YSBhbmQgbWlSTkEgY291bnRzCmBgYHtyfQpzYW1wbGVzIDwtIHJlYWQuY3N2KCdkYXRhL3BsYXNtYV9zYW1wbGVzLmNzdicpCmNvdW50cyA8LSByZWFkLmNzdignZGF0YS9wbGFzbWFfbWlyX2NvdW50cy5jc3YnKQpzYW1wbGVzIDwtIHN1YnNldChzYW1wbGVzLCBMaWJyYXJ5LkdlbmVyYXRpb24uU2V0ICE9ICJTZXRSZWNoZWNrIiApICAjIGV4Y2x1ZGUgU2V0UmVjaGVjayBzYW1wbGVzCnNhbXBsZXMgPC0gc2FtcGxlc1tzYW1wbGVzJFggIT0gIlgxMSIgJiBzYW1wbGVzJFggIT0gIlg5MiIsXSAjIHJlbW92ZSBvdXRsaWVycwpzYW1wbGVzJFBhcnRpY2lwYW50LklEIDwtIGZhY3RvcihzYW1wbGVzJFBhcnRpY2lwYW50LklEKSAgIyBJRCBpcyBjYXRlZ29yaWNhbCwgbm90IG51bWVyaWNhbApyb3duYW1lcyhjb3VudHMpID0gY291bnRzJFgKcm93bmFtZXMoc2FtcGxlcykgPSBzYW1wbGVzJFgKY291bnRzJFggPC0gTlVMTCAgIyByZW1vdmUgZXh0cmEgY29sdW1uCnNhbXBsZXMkWCA8LSBOVUxMCmNvdW50cyA8LSBjb3VudHNbLHVuaXF1ZShyb3duYW1lcyhzYW1wbGVzKSldCmhlYWQoc2FtcGxlcykKaGVhZChjb3VudHMpCmBgYAoKIyMgRmlsdGVyCmBgYHtyfQpsaWJyYXJ5KGVkZ2VSKQpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH4wK1JhY2UrR2VuZGVyK0FnZSwgc2FtcGxlcykKZGdlID0gREdFTGlzdChjb3VudHMgPSBjb3VudHMsIHNhbXBsZXMgPSBzYW1wbGVzKQojIHJlcXVpcmUgbWlSTkFzIHRvIGhhdmUgQ1BNID4gMSBpbiBhdCBsZWFzdCAyIHNhbXBsZXMKY291bnRzUGVyTWlsbGlvbiA8LSBlZGdlUjo6Y3BtKGRnZSkKY291bnRDaGVjayA8LSBjb3VudHNQZXJNaWxsaW9uID4gMQpoZWFkKGNvdW50Q2hlY2spCmtlZXAgPC0gd2hpY2gocm93U3Vtcyhjb3VudENoZWNrKSA+PSAyKSAKZGdlIDwtIGRnZVtrZWVwLF0KYGBgCiMjIEV4cGxvcmUgdmFyaWFuY2UKYGBge3J9CmxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCmxpYnJhcnkoc2NhdGVyKQpyZWFkc19zY2UgPC0gU2luZ2xlQ2VsbEV4cGVyaW1lbnQoYXNzYXlzPWxpc3QoY291bnRzPWRnZSRjb3VudHMpLCAgY29sRGF0YT1kZ2Ukc2FtcGxlcykKIyByZW1vdmUgdW5leHByZXNzZWQgbWlSTkFzCmtlZXBfZmVhdHVyZSA8LSByb3dTdW1zKGNvdW50cyhyZWFkc19zY2UpID4gMCkgPiAwCnJlYWRzX3NjZSA8LSByZWFkc19zY2Vba2VlcF9mZWF0dXJlLCBdCnJlYWRzX3NjZSA8LSBjYWxjdWxhdGVRQ01ldHJpY3MocmVhZHNfc2NlKQojIGxvZyB0cmFuc2Zvcm0KY3BtKHJlYWRzX3NjZSkgPC0gY2FsY3VsYXRlQ1BNKHJlYWRzX3NjZSkKcmVhZHNfc2NlIDwtIG5vcm1hbGl6ZShyZWFkc19zY2UpCmxvZ2NvdW50cyhyZWFkc19zY2UpIDwtIGxvZzIoY2FsY3VsYXRlQ1BNKHJlYWRzX3NjZSkgKyAxKQojIHZpc3VhbGl6ZQpoaXN0KHJlYWRzX3NjZSR0b3RhbF9jb3VudHMsIGJyZWFrcz0xMDApICAjIGNvdW50cyBwZXIgc2FtcGxlCmhpc3QocmVhZHNfc2NlJHRvdGFsX2ZlYXR1cmVzLCBicmVha3M9MTAwKSAgIyBjb3VudHMgcGVyIG1pUk5BCnBsb3RRQyhyZWFkc19zY2UsIHR5cGUgPSAiaGlnaGVzdC1leHByZXNzaW9uIikKcGxvdFFDKHJlYWRzX3NjZSwgdHlwZT0iZXhwbGFuYXRvcnktdmFyaWFibGVzIiwgdmFyaWFibGVzPWMoIkluZGV4IiwgIlBhcnRpY2lwYW50LklEIiwgIkNvbGxlY3Rpb24uRGF0ZSIsICJMaWJyYXJ5LkdlbmVyYXRpb24uU2V0IiwgIk1pU2VxLlFDLlJ1biIsICd0b3RhbF9mZWF0dXJlcycsICJBZ2UiLCAiUmFjZSIsICJTZXgiKSkKYGBgCiMjIEV4YW1pbmUgc291cmNlcyBvZiB2YXJpYXRpb24KYGBge3J9CnBsb3RQQ0EocmVhZHNfc2NlLCBleHByc192YWx1ZXMgPSAibG9nY291bnRzIiwgY29sb3VyX2J5ID0gIkxpYnJhcnkuR2VuZXJhdGlvbi5TZXQiLCBzaXplX2J5ID0gInRvdGFsX2ZlYXR1cmVzIikKcGxvdFBDQShyZWFkc19zY2UsIGV4cHJzX3ZhbHVlcyA9ICJsb2djb3VudHMiLCBjb2xvdXJfYnkgPSAiUmFjZSIsIHNoYXBlX2J5PSJHZW5kZXIiLCBzaXplX2J5ID0gInRvdGFsX2ZlYXR1cmVzIikKZm9yICh2YXIgaW4gYygidG90YWxfZmVhdHVyZXMiLCAiQWdlIiwgIkxpYnJhcnkuR2VuZXJhdGlvbi5TZXQiLCAiSW5kZXgiLCAiUGFydGljaXBhbnQuSUQiLCAiUmFjZSIsICJHZW5kZXIiLCAiQ29sbGVjdGlvbi5EYXRlIikpIHsKICBwcmludCgKICAgIHBsb3RRQyhyZWFkc19zY2UsIHR5cGUgPSAiZmluZC1wY3MiLCBleHByc192YWx1ZXMgPSAibG9nY291bnRzIiwgdmFyaWFibGUgPSB2YXIpCiAgICApICArIGdndGl0bGUodmFyKSAKICB9CmBgYAoKIyMgTm9ybWFsaXplICYgUmVtb3ZlIHVud2FudGVkIHNvdXJjZXMgb2YgdmFyaWF0aW9uCmBgYHtyfQpsaWJyYXJ5KFJVVlNlcSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KG12b3V0bGllcikKZGdlIDwtIGNhbGNOb3JtRmFjdG9ycyhkZ2UpCmRnZSA8LSBlc3RpbWF0ZUdMTUNvbW1vbkRpc3AoZGdlLCBkZXNpZ24pCmRnZSA8LSBlc3RpbWF0ZUdMTVRhZ3dpc2VEaXNwKGRnZSwgZGVzaWduKQpmaXQgPC0gZ2xtRml0KGRnZSwgZGVzaWduKQpyZXMgPC0gcmVzaWR1YWxzKGZpdCwgdHlwZT0iZGV2aWFuY2UiKQpzZXQgPC0gbmV3U2VxRXhwcmVzc2lvblNldChkZ2UkY291bnRzLCBwaGVub0RhdGE9ZGdlJHNhbXBsZXMpCnJ1dnJfc2V0cyA8LSBsaXN0KCkKZm9yKGsgaW4gMTo1KSB7CiAgcnV2cl9zZXRzW1trXV0gPC0gUlVWcihzZXQsIHJvdy5uYW1lcyhkZ2UpLCBrPWssIHJlcykKICBhc3NheShyZWFkc19zY2UsIHBhc3RlKCJSVVZyIGs9IiwgdG9TdHJpbmcoaykpKSA8LSBsb2cyKHQodChhc3NheURhdGEocnV2cl9zZXRzW1trXV0pJG5vcm1hbGl6ZWRDb3VudHMpIC8gY29sU3Vtcyhhc3NheURhdGEocnV2cl9zZXRzW1trXV0pJG5vcm1hbGl6ZWRDb3VudHMpICogMWU2KSArIDEpCn0KZm9yKG4gaW4gYXNzYXlOYW1lcyhyZWFkc19zY2UpKSB7CiAgcHJpbnQoCiAgICAgICAgcGxvdFBDQSgKICAgICAgICAgICAgcmVhZHNfc2NlLAogICAgICAgICAgICBjb2xvdXJfYnkgPSAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsCiAgICAgICAgICAgIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLAogICAgICAgICAgICBleHByc192YWx1ZXMgPSBuCiAgICAgICAgKSArIGdndGl0bGUocGFzdGUoIkJhdGNoIixuKSkKICApCiAgcHJpbnQoCiAgICAgICAgcGxvdFBDQSgKICAgICAgICAgICAgcmVhZHNfc2NlLAogICAgICAgICAgICBjb2xvdXJfYnkgPSAiUmFjZSIsCiAgICAgICAgICAgIHNoYXBlX2J5ID0gIkdlbmRlciIsCiAgICAgICAgICAgIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLAogICAgICAgICAgICBleHByc192YWx1ZXMgPSBuCiAgICAgICAgKSArIGdndGl0bGUocGFzdGUoIkRlbW9ncmFwaGljcyIsIG4pKSAgICAgICAgCiAgKQp9CmBgYAoKIyMgRGV0ZWN0IG91dGxpZXJzCmBgYHtyfQpyZWFkc19zY2UgPC0gcnVuUENBKHJlYWRzX3NjZSwgdXNlX2NvbGRhdGEgPSBUUlVFLCBkZXRlY3Rfb3V0bGllcnMgPSBUUlVFKQpvdXRsaWVycyA8LSBjb2xuYW1lcyhyZWFkc19zY2UpW3JlYWRzX3NjZSRvdXRsaWVyXQpoZWFkKG91dGxpZXJzKQpgYGAKCiMjIEV4YW1pbmUgc291cmNlcyBvZiB2YXJpYW5jZSBhZnRlciByZW1vdmluZyB1bndhbnRlZCB2YXJpYXRpb24KYGBge3J9CmZvciAodmFyIGluIGMoInRvdGFsX2ZlYXR1cmVzIiwgIkFnZSIsICJMaWJyYXJ5LkdlbmVyYXRpb24uU2V0IiwgIkluZGV4IiwgIlBhcnRpY2lwYW50LklEIiwgIlNvdXJjZSIsICJSYWNlIiwgIkdlbmRlciIsICJDb2xsZWN0aW9uLkRhdGUiKSkgewogIHByaW50KAogICAgcGxvdFFDKHJlYWRzX3NjZSwgdHlwZSA9ICJmaW5kLXBjcyIsIGV4cHJzX3ZhbHVlcyA9ICJSVVZyIGs9IDIiLCB2YXJpYWJsZSA9IHZhcikKICAgICkgICsgZ2d0aXRsZSh2YXIpIAp9CmBgYAojIyBWaXN1YWxpemUgdG9wIGhpZ2hseS1leHByZXNzZWQgbWlSTkFzIG1hbGUtdnMtZmVtYWxlCmBgYHtyfQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKbGlicmFyeShyZXNoYXBlMikKIyBSeWFuJ3MgY29kZSwgbW9kaWZpZWQKbm9ybS5leHByLm1hdHIgPC0gZXhwcnMocmVhZHNfc2NlKQojIFJhbmsgdGhlIG1lYW4gZXhwcmVzc2lvbiB2YWx1ZXMgZm9yIHBsYXNtYS9zZXJ1bSBtaVJzLiBIaWdoZXN0IGV4cHJlc3Npb24gPSAxCm1lYW4uZXhwci5tYWxlLnJhbmsgPC0gcmFuaygtMSpyb3dNZWFucyhub3JtLmV4cHIubWF0clssIGNvbERhdGEocmVhZHNfc2NlKSRHZW5kZXI9PSJNYWxlIl0pKQptZWFuLmV4cHIuZmVtYWxlLnJhbmsgPC0gcmFuaygtMSpyb3dNZWFucyhub3JtLmV4cHIubWF0clssIGNvbERhdGEocmVhZHNfc2NlKSRHZW5kZXI9PSJGZW1hbGUiXSkpCnRvcF9OIDwtIDIwCnRvcC5taVJzIDwtIHJvdy5uYW1lcyhub3JtLmV4cHIubWF0cilbbWVhbi5leHByLm1hbGUucmFuayA8PSB0b3BfTiB8IG1lYW4uZXhwci5mZW1hbGUucmFuayA8PSB0b3BfTl0gIyBnZXQgdGhlIG5hbWVzIG9mIHRoZSB0b3AgbWlScyBpbiBwbGFzbWEgb3Igc2VydW0Kbm9ybS5leHByLnRvcCA8LSBub3JtLmV4cHIubWF0clt0b3AubWlScywgXSAjIEdldCB0aGUgZXhwcmVzc2lvbiBtYXRyaXggZm9yIHRoZSB0b3AgbUlScwpub3JtLmV4cHIubWVsdCA8LSByZXNoYXBlMjo6bWVsdChub3JtLmV4cHIudG9wKSAjIENvbnZlcnQgeW91ciBub3JtYWxpemVkIGV4cHJlc3Npb24gbWF0cml4IHRvIGEgMyBjb2x1bW4gZGF0YS5mcmFtZSAocm93Lm5hbWUsIGNvbC5uYW1lLCBleHByZXNzaW9uIHZhbHVlKS4gTWVsdCBpcyBpbiB0aGUgZHB5bHIgcGFja2FnZSwgSSBiZWxpZXZlLgpjb2xuYW1lcyhub3JtLmV4cHIubWVsdCkgPC0gYygibWlSLklEIiwgIk1ULlVuaXF1ZS5JRCIsICJub3JtLmV4cHIiKSAjIGp1c3Qgc28gaXQncyBlYXNpZXIgZm9yIG1lIHRvIHRlbGwgeW91IHdoaWNoIGNvbHVtbnMgSSdtIHVzaW5nLgpub3JtLmV4cHIubWVsdCRHZW5kZXIgPC0gIiIKZm9yIChyb3dfbnVtIGluIDE6bnJvdyhub3JtLmV4cHIubWVsdCkpeyAgIyBQdWxsIHRoZSBHZW5kZXIgdmFsdWVzIGZyb20gdGhlIGNvbHVtbiBtZXRhZGF0YS4KICBtdF91bmlxdWVfaWQgPC0gbm9ybS5leHByLm1lbHRbcm93X251bSwgXSRNVC5VbmlxdWUuSUQKICBub3JtLmV4cHIubWVsdFtyb3dfbnVtLCAiR2VuZGVyIl0gPC0gYXMuY2hhcmFjdGVyKHNhbXBsZXNbbXRfdW5pcXVlX2lkLCJHZW5kZXIiXSkKfQojIFRoaXMgd291bGQgYmUgYSBzaW1wbGUgYm94cGxvdCB3aXRoIHBsYXNtYS9zZXJ1bSBzaWRlLWJ5LXNpZGUuIE92ZXJsYXlpbmcgdGhlIGJveGVzIG92ZXIgcG9pbnRzIHRha2VzIGEgbGl0dGxlIG1vcmUgdHdlYWtpbmcgdG8gZ2V0IHRoZSBkb2RnZS93aWR0aCByaWdodCwgYnV0IGl0J3MgZG9hYmxlLgpnZ3Bsb3Qobm9ybS5leHByLm1lbHQsIGFlcyh4PXJlb3JkZXIobWlSLklELCBub3JtLmV4cHIsIEZVTj1tZWRpYW4pLCB5PW5vcm0uZXhwciwgZmlsbD1HZW5kZXIpKSArIGdlb21fYm94cGxvdChwb3M9ImRvZGdlIiwgb3V0bGllci5zaXplPTAuNSkgKyAKICBnZ3RpdGxlKCJUb3AgMjAgRXhwcmVzc2VkIG1pUk5BcyIpICsgeWxhYigiTm9ybWFsaXplZCBFeHByZXNzaW9uIikgKyB4bGFiKCJtaVJOQSBJRCIpICsgCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKHNpemUgPSAwLjUsIGxpbmV0eXBlID0gJ3NvbGlkJywgY29sb3VyID0gImdyZXkiKSwgCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCmdncGxvdChub3JtLmV4cHIubWVsdCwgYWVzKHg9cmVvcmRlcihtaVIuSUQsIG5vcm0uZXhwciwgRlVOPW1lZGlhbiksIHk9bm9ybS5leHByLCBmaWxsPUdlbmRlcikpICsgZ2VvbV9ib3hwbG90KHBvcz0iZG9kZ2UiLCBvdXRsaWVyLnNpemU9MC41KSArIAogIGdndGl0bGUoIlRvcCAyMCBFeHByZXNzZWQgbWlSTkFzIikgKyB5bGFiKCJOb3JtYWxpemVkIEV4cHJlc3Npb24iKSArIHhsYWIoIm1pUk5BIElEIikgKyAKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IDAuNSwgbGluZXR5cGUgPSAnc29saWQnLCBjb2xvdXIgPSAiZ3JleSIpLCAKICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsgY29vcmRfZmxpcCgpCmBgYAojIyBWaXN1YWxpemUgdG9wIGhpZ2hseS1leHByZXNzZWQgbWlSTkFzIGJ5IFJhY2UKYGBge3J9CmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KHJlc2hhcGUyKQojIFJ5YW4ncyBjb2RlLCBtb2RpZmllZApub3JtLmV4cHIubWF0ciA8LSBleHBycyhyZWFkc19zY2UpCiMgUmFuayB0aGUgbWVhbiBleHByZXNzaW9uIHZhbHVlcyBmb3IgcGxhc21hL3NlcnVtIG1pUnMuIEhpZ2hlc3QgZXhwcmVzc2lvbiA9IDEKbWVhbi5leHByLmFmci5yYW5rIDwtIHJhbmsoLTEqcm93TWVhbnMobm9ybS5leHByLm1hdHJbLCBjb2xEYXRhKHJlYWRzX3NjZSkkUmFjZT09IkFmcmljYW5fQW1lcmljYW4iXSkpCm1lYW4uZXhwci5hc24ucmFuayA8LSByYW5rKC0xKnJvd01lYW5zKG5vcm0uZXhwci5tYXRyWywgY29sRGF0YShyZWFkc19zY2UpJFJhY2U9PSJBc2lhbiJdKSkKbWVhbi5leHByLndodC5yYW5rIDwtIHJhbmsoLTEqcm93TWVhbnMobm9ybS5leHByLm1hdHJbLCBjb2xEYXRhKHJlYWRzX3NjZSkkUmFjZT09IldoaXRlIl0pKQp0b3BfTiA8LSAyMAp0b3AubWlScyA8LSByb3cubmFtZXMobm9ybS5leHByLm1hdHIpW21lYW4uZXhwci5hZnIucmFuayA8PSB0b3BfTiB8IG1lYW4uZXhwci5hc24ucmFuayA8PSB0b3BfTiB8IG1lYW4uZXhwci53aHQucmFuayA8PSB0b3BfTl0gIyBnZXQgdGhlIG5hbWVzIG9mIHRoZSB0b3AgbWlScyBpbiBwbGFzbWEgb3Igc2VydW0Kbm9ybS5leHByLnRvcCA8LSBub3JtLmV4cHIubWF0clt0b3AubWlScywgXSAjIEdldCB0aGUgZXhwcmVzc2lvbiBtYXRyaXggZm9yIHRoZSB0b3AgbUlScwpub3JtLmV4cHIubWVsdCA8LSByZXNoYXBlMjo6bWVsdChub3JtLmV4cHIudG9wKSAjIENvbnZlcnQgeW91ciBub3JtYWxpemVkIGV4cHJlc3Npb24gbWF0cml4IHRvIGEgMyBjb2x1bW4gZGF0YS5mcmFtZSAocm93Lm5hbWUsIGNvbC5uYW1lLCBleHByZXNzaW9uIHZhbHVlKS4gTWVsdCBpcyBpbiB0aGUgZHB5bHIgcGFja2FnZSwgSSBiZWxpZXZlLgpjb2xuYW1lcyhub3JtLmV4cHIubWVsdCkgPC0gYygibWlSLklEIiwgIk1ULlVuaXF1ZS5JRCIsICJub3JtLmV4cHIiKSAjIGp1c3Qgc28gaXQncyBlYXNpZXIgZm9yIG1lIHRvIHRlbGwgeW91IHdoaWNoIGNvbHVtbnMgSSdtIHVzaW5nLgpub3JtLmV4cHIubWVsdCRSYWNlIDwtICIiCmZvciAocm93X251bSBpbiAxOm5yb3cobm9ybS5leHByLm1lbHQpKXsgICMgUHVsbCB0aGUgR2VuZGVyIHZhbHVlcyBmcm9tIHRoZSBjb2x1bW4gbWV0YWRhdGEuCiAgbXRfdW5pcXVlX2lkIDwtIG5vcm0uZXhwci5tZWx0W3Jvd19udW0sIF0kTVQuVW5pcXVlLklECiAgbm9ybS5leHByLm1lbHRbcm93X251bSwgIlJhY2UiXSA8LSBhcy5jaGFyYWN0ZXIoc2FtcGxlc1ttdF91bmlxdWVfaWQsIlJhY2UiXSkKfQpub3JtLmV4cHIubWVsdCA8LSBub3JtLmV4cHIubWVsdFtub3JtLmV4cHIubWVsdCRSYWNlICVpbiUgYygiQWZyaWNhbl9BbWVyaWNhbiIsICJBc2lhbiIsICJXaGl0ZSIpLF0KIyBUaGlzIHdvdWxkIGJlIGEgc2ltcGxlIGJveHBsb3Qgd2l0aCBwbGFzbWEvc2VydW0gc2lkZS1ieS1zaWRlLiBPdmVybGF5aW5nIHRoZSBib3hlcyBvdmVyIHBvaW50cyB0YWtlcyBhIGxpdHRsZSBtb3JlIHR3ZWFraW5nIHRvIGdldCB0aGUgZG9kZ2Uvd2lkdGggcmlnaHQsIGJ1dCBpdCdzIGRvYWJsZS4KZ2dwbG90KG5vcm0uZXhwci5tZWx0LCBhZXMoeD1yZW9yZGVyKG1pUi5JRCwgbm9ybS5leHByLCBGVU49bWVkaWFuKSwgeT1ub3JtLmV4cHIsIGZpbGw9UmFjZSkpICsgZ2VvbV9ib3hwbG90KHBvcz0iZG9kZ2UiLCBvdXRsaWVyLnNpemU9MC41KSArIAogIGdndGl0bGUoIlRvcCAyMCBFeHByZXNzZWQgbWlSTkFzIikgKyB5bGFiKCJOb3JtYWxpemVkIEV4cHJlc3Npb24iKSArIHhsYWIoIm1pUk5BIElEIikgKyAKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2xpbmUoc2l6ZSA9IDAuNSwgbGluZXR5cGUgPSAnc29saWQnLCBjb2xvdXIgPSAiZ3JleSIpLCAKICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkKZ2dwbG90KG5vcm0uZXhwci5tZWx0LCBhZXMoeD1yZW9yZGVyKG1pUi5JRCwgbm9ybS5leHByLCBGVU49bWVkaWFuKSwgeT1ub3JtLmV4cHIsIGZpbGw9UmFjZSkpICsgZ2VvbV9ib3hwbG90KHBvcz0iZG9kZ2UiLCBvdXRsaWVyLnNpemU9MC41KSArIAogIGdndGl0bGUoIlRvcCAyMCBFeHByZXNzZWQgbWlSTkFzIikgKyB5bGFiKCJOb3JtYWxpemVkIEV4cHJlc3Npb24iKSArIHhsYWIoIm1pUk5BIElEIikgKyAKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IDAuNSwgbGluZXR5cGUgPSAnc29saWQnLCBjb2xvdXIgPSAiZ3JleSIpLCAKICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsgY29vcmRfZmxpcCgpCmBgYAoKIyBERSBhbmFseXNpcyB3aXRoIEVkZ2VSCmBgYHtyfQpsaWJyYXJ5KHBoZWF0bWFwKQojIGJhc2VkIG9uIFJ5YW4ncyBjb2RlLCBwYXNzIFJVVlNlcS1jb3JyZWN0ZWQgZGF0YSB0byBlZGdlUgpydXZyMiA8LSBydXZyX3NldHNbWzJdXQpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH4gMCArIFJhY2UgKyBHZW5kZXIgKyBBZ2UgKyBXXzEgKyBXXzIsIHBEYXRhKHJ1dnIyKSkKZGdlIDwtIGVzdGltYXRlRGlzcChkZ2UsIGRlc2lnbiA9IGRlc2lnbiwgdGFnd2lzZSA9IFRSVUUsIHJvYnVzdCA9IFRSVUUpCmZpdCA8LSBnbG1GaXQoZGdlLCBkZXNpZ24pCmBgYAoKIyMgQ29udHJhc3QgZ2VuZGVyCmBgYHtyfQpscnQgPC0gZ2xtTFJUKGZpdCwgY29lZiA9ICJHZW5kZXJNYWxlIikKcmVzdWx0cyA8LSBkYXRhLmZyYW1lKHRvcFRhZ3MobHJ0LCBuPUluZiwgc29ydC5ieT0iUFZhbHVlIiwgcC52YWx1ZT0wLjA1KSkKc2lnX21pUnMgPSBsaXN0KCkKbG9nRkNfdGhyZXNob2xkIDwtIDEKc2lnX21pUnNbWzFdXSA8LSByb3duYW1lcyhyZXN1bHRzW3Jlc3VsdHMkUFZhbHVlIDwgMC4wNSAmIGFicyhyZXN1bHRzJGxvZ0ZDKSA+IGxvZ0ZDX3RocmVzaG9sZCxdKSAgIyBmaWx0ZXIgYnkgcC12YWx1ZSBhbmQgbG9nRkMKc2lnX21pUnNbWzJdXSA8LSByb3duYW1lcyhyZXN1bHRzW3Jlc3VsdHMkUFZhbHVlIDwgMC4wNSxdKQojIGhlYXRtYXBzIG9mIHNpZ25pZmljYW50IERFIG1pUk5BcwpHZW5kZXIgPC0gcERhdGEocnV2cjIpWyxjKCJHZW5kZXIiKV0KYW5ub3RhdGlvbnMgPC0gYXMuZGF0YS5mcmFtZShHZW5kZXIpCnJvd25hbWVzKGFubm90YXRpb25zKSA8LSByb3duYW1lcyhwRGF0YShydXZyMikpCmZvcihtaVJfbGlzdCBpbiBzaWdfbWlScykgewogIHBoZWF0bWFwKG5vcm0uZXhwci5tYXRyW21pUl9saXN0LF0sIGNsdXN0ZXJfcm93cz1UUlVFLCBzaG93X3Jvd25hbWVzPVRSVUUsIGNsdXN0ZXJfY29scz1UUlVFLCBhbm5vdGF0aW9uX2NvbD1hbm5vdGF0aW9ucywgbWFpbj0iRGlmZmVyZW50aWFsbHkgRXhwcmVzc2VkIG1pUk5BcyIpCn0KYGBgCgojIyBDb250cmFzdCByYWNlCmBgYHtyfQpscnQgPC0gZ2xtTFJUKGZpdCwgY29udHJhc3Q9bWFrZUNvbnRyYXN0cyhhc249KEFzaWFuLShBZnJpY2FuX0FtZXJpY2FuK1doaXRlKS8yKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWZyPShBZnJpY2FuX0FtZXJpY2FuLShBc2lhbitXaGl0ZSkvMiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdodD0oV2hpdGUtKEFmcmljYW5fQW1lcmljYW4rQXNpYW4pLzIpKSwgbGV2ZWxzID0gZGVzaWduKQpyZXN1bHRzIDwtIGRhdGEuZnJhbWUodG9wVGFncyhscnQsIG49SW5mLCBzb3J0LmJ5PSJQVmFsdWUiLCBwLnZhbHVlPTAuMDUpKQpzaWdfbWlScyA9IGxpc3QoKQpsb2dGQ190aHJlc2hvbGQgPC0gMQpzaWdfbWlSc1tbMV1dIDwtIHJvd25hbWVzKHJlc3VsdHNbcmVzdWx0cyRQVmFsdWUgPCAwLjA1ICYgYWJzKHJlc3VsdHMkbG9nRkMpID4gbG9nRkNfdGhyZXNob2xkLF0pICAjIGZpbHRlciBieSBwLXZhbHVlIGFuZCBsb2dGQwpzaWdfbWlSc1tbMl1dIDwtIHJvd25hbWVzKHJlc3VsdHNbcmVzdWx0cyRQVmFsdWUgPCAwLjA1LF0pCiMgaGVhdG1hcHMgb2Ygc2lnbmlmaWNhbnQgREUgbWlSTkFzClJhY2UgPC0gcERhdGEocnV2cjIpWyxjKCJSYWNlIiwgIkdlbmRlciIpXQphbm5vdGF0aW9ucyA8LSBhcy5kYXRhLmZyYW1lKFJhY2UpCnJvd25hbWVzKGFubm90YXRpb25zKSA8LSByb3duYW1lcyhwRGF0YShydXZyMikpCmZvcihtaVJfbGlzdCBpbiBzaWdfbWlScykgewogIHBoZWF0bWFwKG5vcm0uZXhwci5tYXRyW21pUl9saXN0LF0sIGNsdXN0ZXJfcm93cz1UUlVFLCBzaG93X3Jvd25hbWVzPVRSVUUsIGNsdXN0ZXJfY29scz1UUlVFLCBhbm5vdGF0aW9uX2NvbD1hbm5vdGF0aW9ucywgbWFpbj0iRGlmZmVyZW50aWFsbHkgRXhwcmVzc2VkIG1pUk5BcyIpCn0KYGBgCgojIyBDcmVhdGUgYSBQQ0EgcGxvdCBzaG93aW5nIEFnZSB4IEdlbmRlcgpgYGB7cn0KcGxvdFBDQSgKICAgICAgICAgICAgcmVhZHNfc2NlLAogICAgICAgICAgICBjb2xvdXJfYnkgPSAiQWdlIiwKICAgICAgICAgICAgc2hhcGVfYnkgPSAiR2VuZGVyIiwKICAgICAgICAgICAgc2l6ZV9ieSA9ICJ0b3RhbF9mZWF0dXJlcyIsCiAgICAgICAgICAgIGV4cHJzX3ZhbHVlcyA9ICJSVVZyIGs9IDIiCikgKyBnZ3RpdGxlKCJSVVZTZXEtTm9ybWFsaXplZCBFeHByZXNzaW9uIChrPTIpIikgCmBgYAoKYGBge3J9CnNhdmUuaW1hZ2UoImRpZmZfZXhwcl9wbGFzbWFfb25seS5SRGF0YSIpCmBgYA==